
/*

Design Pattern Automation Toolkit.
Application to create applications with emphasis on Design patterns.
And support for round trip engineering.
Copyright (C) 2004 Vineeth Neelakant. nvineeth@gmail.com

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

*/
using System;
using BusinessLayer;
using InfoClasses;
using dpatoolkit.UILayer;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace dpatoolkit.UILayer
{
	/// <summary>
	/// Represents the relation between 2 UIObjects. This Interface is inherited by UI Aggregates,Inheritance,References.
	/// Most of the members are same as that of UIObject.
	/// </summary>
	public abstract class IUIRelation
	{
		#region fields
		protected IUIObject obj1,obj2;
		protected bool recurs=false;
		protected int xoffset=0,yoffset=0;
		protected Point pxoffset= new Point(0,0),
			pyoffset= new Point(0,0);
		
		protected Point[] points3= new Point[3],//we use 2 different point arrays.. for optimization
			points4 = new Point[4];
		protected string reltext=String.Empty;
			
		protected int numPoints;
		protected ContextMenu relationContextMenu;
		protected IDesignPatternSubject subject;
		protected UIPositionObserver posObserver;
		protected GraphicsPath	path = new GraphicsPath();
		protected bool isDragged = false;
		static protected int editIndex = 0;
		static protected int revIndex = 1;
		static protected int delIndex = 2;
		protected int index;
        protected RectangleF relTextRectF_;
        protected RelationType relationType;
		#endregion
		protected void InitContextMenu ()
		{
			MenuItem edit = new MenuItem("Edit");
			MenuItem rev = new MenuItem("Reverse me!");
			MenuItem del = new MenuItem("Delete");
			this.relationContextMenu = new ContextMenu(
				new MenuItem[]{edit,rev,del});
		}
		public IUIRelation(string relName , IUIObject _o1, IUIObject _o2, UIPositionObserver obs)
		{
			this.reltext=relName;
			this.obj1=_o1;
			this.obj2=_o2;
			
			if( obs == null)
				obs = new UIPositionObserver(this.UIPositionChanged);

			index = obj1.AddUIPositionObserver(this, obs,true);
			obj2.AddUIPositionObserver(this, obs,false);
			this.posObserver = obs;
			this.InitContextMenu();
		}
		
		public virtual void InitDrag()
		{
			this.isDragged = true;
		}
		public virtual void EndDrag()
		{
			this.isDragged = false;
		}
		
		public virtual GraphicsPath Path
		{
			get
			{
				this.path.Reset();
				if(this.numPoints==3)
				{
					path.AddLines(new Point[]{
									 new Point( this.points3[0].X,this.points3[0].Y),
									 new Point( this.points3[0].X,this.points3[0].Y),
									 new Point( this.points3[1].X,this.points3[1].Y),
									 new Point( this.points3[1].X,this.points3[1].Y),
								  
					});
					path.AddLines(new Point[]{
									 new Point( this.points3[1].X,this.points3[1].Y),
									 new Point( this.points3[1].X,this.points3[1].Y),
									 new Point( this.points3[2].X,this.points3[2].Y),
									 new Point( this.points3[2].X,this.points3[2].Y),
								  
					});
				}
				else
				{
					path.AddLines(new Point[]{
									 new Point( this.points4[0].X,this.points4[0].Y),
									 new Point( this.points4[0].X,this.points4[0].Y),
									 new Point( this.points4[1].X,this.points4[1].Y),
									 new Point( this.points4[1].X,this.points4[1].Y),
								  
					});
					path.AddLines(new Point[]{
									 new Point( this.points4[1].X,this.points4[1].Y),
									 new Point( this.points4[1].X,this.points4[1].Y),
									 new Point( this.points4[2].X,this.points4[2].Y),
									 new Point( this.points4[2].X,this.points4[2].Y),
								  
					});
					path.AddLines(new Point[]{
									 new Point( this.points4[2].X,this.points4[2].Y),
									 new Point( this.points4[2].X,this.points4[2].Y),
									 new Point( this.points4[3].X,this.points4[3].Y),
									 new Point( this.points4[3].X,this.points4[3].Y),
								  
					});
				}
				return path;
			}
		}
		public virtual  bool Contains(Point p)
		{
			//make the collision detection slightly inaccurate,
			//or else the user has to click with single pixel accuracy 
			//allow some fuzzy , and detect the point even if it is clicked
			//2 or 3 pixels around the line.
			int fuzzy=3;
			// first check if the click was made on the relation text, 
            // since it is natural to the user to click on the relation text
            // rather than on the lines.
            if (this.reltext != String.Empty && this.relTextRectF_.Contains(p))
                return true;
			if(this.numPoints==3)
			{
				GraphicsPath path1 = new GraphicsPath();
				GraphicsPath path2 = new GraphicsPath();
				path1.AddLines(new Point[]{
								  new Point( this.points3[0].X-fuzzy,this.points3[0].Y-fuzzy),
								  new Point( this.points3[0].X+fuzzy,this.points3[0].Y+fuzzy),
								  new Point( this.points3[1].X+fuzzy,this.points3[1].Y+fuzzy),
								  new Point( this.points3[1].X-fuzzy,this.points3[1].Y-fuzzy),
								  
				});
				path2.AddLines(new Point[]{
								  new Point( this.points3[1].X-fuzzy,this.points3[1].Y-fuzzy),
								  new Point( this.points3[1].X+fuzzy,this.points3[1].Y+fuzzy),
								  new Point( this.points3[2].X+fuzzy,this.points3[2].Y+fuzzy),
								  new Point( this.points3[2].X-fuzzy,this.points3[2].Y-fuzzy),
								  
				});
				return path1.IsVisible(p) || path2.IsVisible(p);
			}
			else
			{
				GraphicsPath path1 = new GraphicsPath();
				GraphicsPath path2 = new GraphicsPath();
				GraphicsPath path3 = new GraphicsPath();
				path1.AddLines(new Point[]{
								  new Point( this.points4[0].X-fuzzy,this.points4[0].Y-fuzzy),
								  new Point( this.points4[0].X+fuzzy,this.points4[0].Y+fuzzy),
								  new Point( this.points4[1].X+fuzzy,this.points4[1].Y+fuzzy),
								  new Point( this.points4[1].X-fuzzy,this.points4[1].Y-fuzzy),
								  
				});
				path2.AddLines(new Point[]{
								  new Point( this.points4[1].X-fuzzy,this.points4[1].Y-fuzzy),
								  new Point( this.points4[1].X+fuzzy,this.points4[1].Y+fuzzy),
								  new Point( this.points4[2].X+fuzzy,this.points4[2].Y+fuzzy),
								  new Point( this.points4[2].X-fuzzy,this.points4[2].Y-fuzzy),
								  
				});
				path3.AddLines(new Point[]{
								  new Point( this.points4[2].X-fuzzy,this.points4[2].Y-fuzzy),
								  new Point( this.points4[2].X+fuzzy,this.points4[2].Y+fuzzy),
								  new Point( this.points4[3].X+fuzzy,this.points4[3].Y+fuzzy),
								  new Point( this.points4[3].X-fuzzy,this.points4[3].Y-fuzzy),
								  
				});
				return path1.IsVisible(p) || path2.IsVisible(p) || path3.IsVisible(p);

			}
		}

		public string RelationName
		{
			get{ return this.reltext;}
			set{this.reltext=value;}
		}

		public abstract void Draw(Graphics g);
		public abstract ContextMenu ContextMenu{get;set;}

		protected void InvalidateLine(Point a, Point b,System.Windows.Forms.Control wnd)
		{
			Rectangle rec;
			int extra_pixels = 6, mul_factor = 3;
			int x,y,width,height;
			
			x = a.X - extra_pixels;
			y = a.Y - extra_pixels;
			
			if(a.X == b.X)
			{
				width	= mul_factor *extra_pixels;
				height	= b.Y-a.Y + mul_factor * extra_pixels;
			}
			else
			{
				height	= mul_factor * extra_pixels;
				width	= b.Y-a.Y + mul_factor *extra_pixels;
			}
			
			rec = new Rectangle(x,y,width,height);
			wnd.Invalidate(rec);
		}

		/* This function is called to invalidate the drawing area
		 * taken by the ui relation
		 * */
		public void Invalidate( System.Windows.Forms.Control wnd)
		{
			/* 3 lines */
			if( numPoints==3)
			{
				this.InvalidateLine(this.points3[0],this.points3[1],wnd);
				this.InvalidateLine(this.points3[1],this.points3[2],wnd);
			}
			else
			{
				this.InvalidateLine(this.points4[0],this.points4[1],wnd);
				this.InvalidateLine(this.points4[1],this.points4[2],wnd);
				this.InvalidateLine(this.points4[2],this.points4[3],wnd);
			}
		}

        protected void ComputeRelationTextRect(Graphics g)
        {
            if (this.reltext == String.Empty || this.isDragged)
                return;
            else
            {
                SizeF size;
                size = UIRenderer.MeasureString(this.reltext, g, UIRenderer.RelationFont);
                // set the size of the rectangle.
                this.relTextRectF_.Size = size;
                // set the x,y co-ordinates.
                if (this.numPoints == 4)
                {
                    // if there are 4 points, then the center of the rectangle 
                    // will be placed at the center of the horizontal line.
                    relTextRectF_.X = (this.points4[1].X + this.points4[2].X) / 2 - relTextRectF_.Width / 2;
                    relTextRectF_.Y = this.points4[1].Y - relTextRectF_.Height;
                }
                if (this.numPoints == 3)
                {
                    int offset = 8;
                    /*
                     *          *
                     *          *
                     *   I Text * Text II
                     * *********************         
                     * III Text * Text IV
                     *          * 
                     *          *
                     * */
                    relTextRectF_.X = this.points3[1].X + offset;
                    relTextRectF_.Y = this.points3[1].Y - relTextRectF_.Height;
                }
                return;
            }

        }

		protected void DrawLines (Graphics g, Pen scPen , Pen ecPen , Pen ncPen)
		{
//			if (this.isDragged)
//				scPen = ecPen = ncPen = Pens.SlateGray;
			this.index = this.obj1.GetIndex(this);
            this.ComputeRelationTextRect(g);

			if( numPoints==3)
			{
				if(!recurs)
				{
					g.DrawLine(scPen,this.points3[0],this.points3[1]);
					g.DrawLine(ecPen,this.points3[1],this.points3[2]);
                    if (!this.isDragged)
                    {
                        g.DrawString(reltext, UIRenderer.RelationFont,
                            scPen.Brush,this.relTextRectF_);
                    }
					return;
				}
				g.DrawLine(ecPen,this.points3[1],this.points3[0]);
				g.DrawLine(scPen,this.points3[2],this.points3[1]);
				if (!this.isDragged)
                    g.DrawString(reltext, UIRenderer.RelationFont, scPen.Brush,
                        this.relTextRectF_);
				return;
			}

			if(!recurs)
			{
				g.DrawLine(scPen,this.points4[0],this.points4[1]);
				g.DrawLine(ncPen,this.points4[1],this.points4[2]);
				g.DrawLine(ecPen,this.points4[2],this.points4[3]);
				if (!this.isDragged)
					g.DrawString(reltext,UIRenderer.RelationFont,scPen.Brush,
                        this.relTextRectF_);
				return;
			}
			g.DrawLine(ecPen,this.points4[1],this.points4[0]);
			g.DrawLine(ncPen,this.points4[1],this.points4[2]);
			g.DrawLine(scPen,this.points4[3],this.points4[2]);

			if(!this.isDragged)
				g.DrawString(reltext,UIRenderer.RelationFont,
                    scPen.Brush, this.relTextRectF_);

			return;
		}
		public abstract void SetPariticapants(Rectangle r1,Rectangle r2);
        public string GetIntellisenseInfo(Point p)
        {
            if (this.Contains(p))
            {
                string msg = this.relationType.ToString();
                if (this.reltext != string.Empty)
                    msg += " : " + this.reltext;
                return msg;
            }
            return string.Empty;
        }

		public  void ComputePath( Rectangle r1, Rectangle r2)
		{
			int x1=r1.X,x2=r2.X,
				y1=r1.Y,y2=r2.Y,
				w1=r1.Width,h1=r1.Height,
				w2=r2.Width,h2=r2.Height;
			int xoffset1,xoffset2, yoffset1, yoffset2;
			
			if(!recurs)
			{
				xoffset1 = this.obj1.GetUIXoffset(this);
				xoffset2 = this.obj2.GetUIXoffset(this);
				yoffset1 = this.obj1.GetUIYoffset(this);
				yoffset2 = this.obj2.GetUIYoffset(this);
			}
			else
			{
				xoffset2 = this.obj1.GetUIXoffset(this);
				xoffset1 = this.obj2.GetUIXoffset(this);
				yoffset2 = this.obj1.GetUIYoffset(this);
				yoffset1 = this.obj2.GetUIYoffset(this);
			}
		
			if( x1+w1 < x2)//left of the rectangle.
			{
				/* [r1] 
				 *		[r2] */
				
				if(y1+h1 < y2)
				{
					/*
					 * [r1]
					 *  | 
					 *  -------------->[r2]
					 * */
					this.points3[0].X = x1+xoffset1;
					this.points3[0].Y = y1+h1;
					this.points3[1].X = this.points3[0].X;
					this.points3[1].Y = y2+yoffset2;
					this.points3[2].X = x2;
					this.points3[2].Y = this.points3[1].Y;
					this.numPoints=3;
					return;
				}
				if(y1 > y2+h2)
				{
					/*
					 *  |----------------->[r2]
					 *  |
					 * [r1]
					 * */
					this.points3[0].X = x1+xoffset1;
					this.points3[0].Y = y1;
					this.points3[1].X = this.points3[0].X;
					this.points3[1].Y = y2+yoffset2;
					this.points3[2].X = x2;
					this.points3[2].Y = this.points3[1].Y;
					this.numPoints=3;
					return;
				}
				/*			p2------->[r2]point3	
				 *			|
				 *			|
				 *	point0[r1]------p1
				 *	
				 * */

				this.points4[0].X = x1+w1;
				this.points4[0].Y = y1+yoffset1;
				this.points4[1].X = this.points4[0].X+(x2-this.points4[0].X)/2;
				this.points4[1].Y = this.points4[0].Y;
				this.points4[2].X = this.points4[1].X;
				this.points4[2].Y = y2+yoffset2;
				this.points4[3].X = x2;
				this.points4[3].Y = this.points4[2].Y;
				this.numPoints = 4;
				return;
			}
			else if (x1+w1 <= x2+w2)
			{
				/*
				 * [r1]
				 *   [r2]
				 * */

				if (y1+h1 < y2)
				{
					/* [r1]
					 *  |
					 *  |---|
					 *	|
					 *	[r2]
					 * */

					this.points4[0].X = x1+xoffset1;
					this.points4[0].Y = y1+h1;
					this.points4[3].X = x2+xoffset2;
					this.points4[3].Y = y2;
					this.points4[1].X = this.points4[0].X;
					this.points4[1].Y = (y1+h1+y2)/2 + index*5;
					this.points4[2].X = this.points4[3].X;
					this.points4[2].Y = this.points4[1].Y;
					this.numPoints=4;
					return;
				}
				if (y1>y2+h2)
				{
					/*	    [r2]
					 *  |-------|	
					 *  |
					 * [r1]
					 * 
					 * */

					this.points4[0].X = x1+xoffset1;
					this.points4[0].Y = y1;
					this.points4[3].X = x2+xoffset2;
					this.points4[3].Y = y2+h2;
					this.points4[1].X = this.points4[0].X;
					this.points4[1].Y = (y2+h2+y1)/2+index*5;//right
					this.points4[2].X = this.points4[3].X;
					this.points4[2].Y = this.points4[1].Y;//right
					this.numPoints=4;
					return;
				}
				/*
				 * NOT SURE!
				 *	  |--------[r2]	
				 * [r1]---|
				 * 
				 * */

				this.points4[0].X = x1+w1;
				this.points4[0].Y = y1+yoffset1;
				this.points4[1].X = (x1+w1+x2)/2;
				this.points4[1].Y = y1+yoffset1+index * 5;
				this.points4[2].X = this.points4[1].X;
				this.points4[2].Y = y2+yoffset2+index * 5;
				this.points4[3].X = x2;
				this.points4[3].Y = this.points4[2].Y;
				this.numPoints=4;
				
				return;
				
			}
			this.recurs=true;
			this.ComputePath(r2,r1);
		}
		protected void UIPositionChanged(object obj)
		{
			recurs=false;
			if( this.obj1.BoundingRectangle.Width==0 || 
				this.obj1.BoundingRectangle.Height==0||
				this.obj2.BoundingRectangle.Width==0 || 
				this.obj2.BoundingRectangle.Height==0)
				return;
			this.ComputePath(this.obj1.BoundingRectangle,
				this.obj2.BoundingRectangle);
		}
		protected void UnRegisterObserver()
		{
			this.obj1.RemoveUIPositionObserver(this,this.posObserver);
			this.obj2.RemoveUIPositionObserver(this,this.posObserver);
		}
	}
}

